<template>
  <view
    class="image-compresser"
    v-if="canvasId"
  >
    <canvas
      :canvas-id="canvasId"
      :style="{ width: canvasSize.width, height: canvasSize.height }"
    ></canvas>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        pic: "",
        canvasSize: {
          width: 0,
          height: 0,
        },
        canvasId: "",
      };
    },
    mounted() {
      // 创建 canvasId
      if (!uni || !uni.imageCompresserCanvasIndex) {
        uni.imageCompresserCanvasIndex = 1;
      } else {
        uni.imageCompresserCanvasIndex++;
      }
      this.canvasId = `compress-canvas${uni.imageCompresserCanvasIndex}`;
    },
    methods: {
      // 压缩
      compressImage(options) {
        return new Promise(async (resolve, reject) => {
          // 等待图片信息
          let info = await this.getImageInfo(options.src)
            .then((info) => info)
            .catch(() => null);

          if (!info) {
            reject("获取图片信息异常");
            return;
          }

          // 设置最大 & 最小 尺寸
          const maxSize = options.maxSize || 1080;
          const minSize = options.minSize || 640;

          // 当前图片尺寸
          let { width, height } = info;

          // 非 H5 平台进行最小尺寸校验
          // #ifndef H5
          if (width <= minSize && height <= minSize) {
            resolve(options.src);
            return;
          }
          // #endif

          // 最大尺寸计算
          //（图像的宽度和高度是否超过最大尺寸。如果其中任一维度超过最大尺寸，代码将对图像进行调整，以使其适应最大尺寸并保持其宽高比。）
          // 这样可以确保图像在调整大小后仍保持原始比例，并且不会超过指定的最大尺寸

          if (width > maxSize || height > maxSize) {
            if (width > height) {
              height = Math.floor(height / (width / maxSize));
              width = maxSize;
            } else {
              width = Math.floor(width / (height / maxSize));
              height = maxSize;
            }
          }

          // 设置画布尺寸
          this.canvasSize.width = `${width}px`;
          this.canvasSize.height = `${height}px`;
          /*           this.$set(this, "canvasSize", {
            width: `${width}px`,
            height: `${height}px`,
          }); */

          // Vue.nextTick 回调在 App 有异常，则使用 setTimeout 等待DOM更新
          setTimeout(() => {
            // 创建 canvas 绘图上下文（指定 canvasId）。在自定义组件下，第二个参数传入组件实例this，以操作组件内 <canvas/> 组件
            // Tip: 需要指定 canvasId，该绘图上下文只作用于对应的 <canvas/>
            const ctx = uni.createCanvasContext(this.canvasId, this);
            // 清除画布上在该矩形区域内的内容。（x，y，宽，高）
            ctx.clearRect(0, 0, width, height);
            // 绘制图像到画布。（所要绘制的图片资源，x，y，宽，高）
            ctx.drawImage(info.path, 0, 0, width, height);
            // 将之前在绘图上下文中的描述（路径、变形、样式）画到 canvas 中。
            // 本次绘制是否接着上一次绘制，即reserve参数为false，则在本次调用drawCanvas绘制之前native层应先清空画布再继续绘制；若reserver参数为true，则保留当前画布上的内容，本次调用drawCanvas绘制的内容覆盖在上面，默认 false
            // 绘制完成后回调
            ctx.draw(false, () => {
              // 把当前画布指定区域的内容导出生成指定大小的图片，并返回文件路径。在自定义组件下，第二个参数传入自定义组件实例，以操作组件内 <canvas> 组件。
              uni.canvasToTempFilePath(
                {
                  x: 0, //画布x轴起点（默认0）
                  y: 0, //画布y轴起点（默认0）
                  width: width, //画布宽度（默认为canvas宽度-x）
                  height: height, //画布高度（默认为canvas高度-y
                  destWidth: width, //图片宽度（默认为 width * 屏幕像素密度）
                  destHeight: height, //输出图片高度（默认为 height * 屏幕像素密度）
                  canvasId: this.canvasId, //画布标识，传入 <canvas/> 的 canvas-id（支付宝小程序是id、其他平台是canvas-id）
                  fileType: options.fileType || "png", //目标文件的类型，只支持 'jpg' 或 'png'。默认为 'png'
                  quality: options.quality || 0.9, //图片的质量，取值范围为 (0, 1]，不在范围内时当作1.0处理
                  success: (res) => {
                    // 在H5平台下，tempFilePath 为 base64
                    resolve(res.tempFilePath);
                  },
                  fail: (err) => {
                    reject(null);
                  },
                },
                this,
              );
            });
          }, 300);
        });
      },
      // 获取图片信息
      getImageInfo(src) {
        return new Promise((resolve, reject) => {
          uni.getImageInfo({
            src,
            success: (info) => {
              resolve(info);
            },
            fail: (err) => {
              console.log(err, "err===获取图片信息");
              reject(null);
            },
          });
        });
      },
      // 批量压缩
      async compressMultiple(options) {
        // 初始化状态变量
        let [index, done, fail] = [0, 0, 0];
        let paths = [];

        // 处理待压缩图片列表
        let waitList = Array.isArray(options.src) ? options.src : [options.src];

        // 批量压缩方法
        let batch = async () => {
          while (index < waitList.length) {
            try {
              const path = await next();
              done++;
              paths.push(path);
              options.progress?.({ done, fail, count: waitList.length });
            } catch (error) {
              fail++;
              options.progress?.({ done, fail, count: waitList.length });
            }
            index++;
          }
        };

        // 单个图片压缩方法
        const next = () => {
          const currentSrc = waitList[index];
          return this.compressImage({
            src: currentSrc,
            maxSize: options.maxSize,
            fileType: options.fileType,
            quality: options.quality,
            minSize: options.minSize,
          });
        };
        // 返回Promise并处理结果
        return new Promise((resolve, reject) => {
          try {
            batch()
              .then(() => {
                if (typeof options.src === "string") {
                  resolve(paths[0]);
                } else {
                  resolve(paths);
                }
              })
              .catch((error) => {
                reject(error);
              });
          } catch (error) {
            reject(error);
          }
        });
      },

      async compress(options) {
        if (options.multiple) {
          return this.compressMultiple(options);
        } else {
          return this.compressImage(options);
        }
      },
    },
  };
</script>

<style lang="scss" scoped>
  .image-compresser {
    position: fixed;
    width: 12px;
    height: 12px;
    overflow: hidden;
    top: -99999px;
    left: 0;
  }
</style>
